package com.ck.core.ffmpeg;

import com.alibaba.fastjson.JSONObject;
import com.ck.common.model.OperationResult;
import com.ck.core.constant.BusinessConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 音视频等转换工具类
 *
 * @author linqiang
 * @version v1.0
 * @Description
 * @date 2019-04-18 14:06
 */
public class FfmpegUtil {

    private static Logger logger = LoggerFactory.getLogger(FfmpegUtil.class);

    /**
     * 调节音量到指定最大音量值
     *
     * @param inputFilePath  输入文件地址
     * @param outputFilePath 输出文件地址
     * @param maxVolume      最大音量值(单位：dB)
     * @param qscale         <数值> 以<数值>质量为基础的VBR，取值0.01-255，约小质量越好
     * @throws FfmpegCommandException
     */
    public static void ajustVolume(String ffmpegCommandPath, String inputFilePath, String outputFilePath, Float maxVolume, String qscale, String vcodec) throws FfmpegCommandException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        logger.info("**************开始调节音量【" + inputFilePath + "】**************");
        //构建获取最大音量命令
        FfmpegCommandBuilder getMaxVolumeCommandBuilder = new FfmpegCommandBuilder(ffmpegCommandPath);
        FfmpegCommand getMaxVolumeCommand = getMaxVolumeCommandBuilder.input(inputFilePath).volumeDetect().build();
        List<String> strings = getMaxVolumeCommand.startReturnList();

        Map<String, Object> contentMap = parseContentToMap(strings);
        if (null == contentMap || contentMap.size() == 0) {
            throw new FfmpegCommandException("解析文件【" + inputFilePath + "】失败");
        }

        logger.info("原视频信息：" + contentMap.toString());

        //视频文件最大音量
        Object volume = contentMap.get(FfmpegEnum.MAX_VOLUME.getValue());
        if (null == volume) {
            logger.warn("原视频无声音, 无需调节");
            return;
        } else {
            float maxVolumeInFile = Float.parseFloat(volume.toString());
            //计算需要调节的音量值
            float adjustVolume = maxVolume - maxVolumeInFile;
            logger.info("设置前最大音量：" + maxVolumeInFile + "， 设置最大音量：" + maxVolume + "， 需要调节的音量：" + adjustVolume);
            //构建调节音量命令
            FfmpegCommandBuilder setVolumeCommandBuilder = new FfmpegCommandBuilder(ffmpegCommandPath);
            FfmpegCommand setVolumeCommand = setVolumeCommandBuilder
                    //输入文件地址
                    .input(inputFilePath)

                    //开启多个线程执行
                    .threads("2")

                    //<数值> 以<数值>质量为基础的VBR，取值0.01-255，越小视频画质越好, 如果用了-vcodec参数则不需要改参数
                .qscale(qscale)

                    //需要修正的音量值（正数为提升，负数为降低音量）
                    .volume(adjustVolume)
                    //覆盖输出文件
                    .override()
                    // 复制文件编解码格式，可使操作时间大大减少，且视频质量基本不变
                    .vcodec(vcodec)
                    //输出文件地址
                    .output(outputFilePath)
                    .build();
            setVolumeCommand.start();

            //再输出文件的信息
//            FfmpegCommandBuilder bb = new FfmpegCommandBuilder(ffmpegCommandPath);
//            FfmpegCommand bbc = bb.input(outputFilePath).volumeDetect().build();
//            List<String> ss = bbc.startReturnList();
//            Map<String, Object> contentMap11 = parseContentToMap(ss);
//            if (contentMap11 != null) {
//                logger.info("转换后视频信息：" + contentMap11.toString());
//            }
        }

        stopWatch.stop();
        long total = stopWatch.getTotalTimeMillis();
        logger.info("**************调节音量【" + inputFilePath + "】用时：" + total + "ms**************");
    }

    /**
     * 获取最大音量
     *
     * @param content 命令得到的内容
     * @return
     */
    private static Map<String, Object> parseContentToMap(List<String> content) {
        if (CollectionUtils.isEmpty(content)) {
            return null;
        }
        Map<String, Object> contentMap = new HashMap<>();
        // 匹配视频时长
        String regexDuration = "Duration: (.*?), start: (.*?), bitrate: (\\d*) kb\\/s";
        // 匹配数据速率和分辨率和视频格式
        String vbitrate = "Stream (.*?): Video: (.*?), (\\d*)x(\\d*)(.*?), (\\d*) kb\\/s, (.*?)";
        // 匹配输入文件信息
        String input = "Input (.*?)";
        // 匹配输出文件信息
        String output =  "Output (.*?)";
        // 匹配允许的视频格式
        String passmuster = ".*("+BusinessConstant.VIDEO_PASS+").*";
        boolean lock = false;
        Pattern pattern = Pattern.compile(regexDuration);
        Pattern pattern2 = Pattern.compile(vbitrate);
        Pattern pattern_input = Pattern.compile(input);
        Pattern pattern_output = Pattern.compile(output);
        // 初始化视频格式不通过
        contentMap.put(FfmpegEnum.PASSMUSTER.getValue(),BusinessConstant.VIDEO_PASS_FAIL);
        for (String line : content) {
            //最大音量
            if (line.contains("max_volume") && line.contains(":")) {
                String text = line.split(":")[1];
                String maxVolume = text.replace("dB", "").trim();
                if (!StringUtils.isEmpty(maxVolume)) {
                    contentMap.put(FfmpegEnum.MAX_VOLUME.getValue(), Float.parseFloat(maxVolume));
                }
            } else {
                //从视频信息中解析时长
                Matcher m = pattern.matcher(line);
                if (m.find()) {
                    int time = getDuration(m.group(1));
                    contentMap.put(FfmpegEnum.DUIRATION_HHSSMM.getValue(), m.group(1));
                    contentMap.put(FfmpegEnum.DUIRATION.getValue(), time);
                    contentMap.put(FfmpegEnum.START.getValue(), m.group(2));
                    contentMap.put(FfmpegEnum.BITRATE.getValue(), m.group(3));
                }
                // 从视频信息中解析视频数据速率
                Matcher m2 = pattern2.matcher(line);
                if(m2.find() && lock){
                    contentMap.put(FfmpegEnum.VBITRATE.getValue(),m2.group(6));
                    contentMap.put(FfmpegEnum.XRESOLUTION.getValue(),m2.group(3));
                    contentMap.put(FfmpegEnum.YRESOLUTION.getValue(),m2.group(4));
                    // 判断视频是否需要转码(判断是否符合要求格式)
                    if(Pattern.matches(passmuster,line)){
                        contentMap.put(FfmpegEnum.PASSMUSTER.getValue(),BusinessConstant.VIDEO_PASS_SUCCESS);
                    }
                }
                // 判断是否应该匹配对应的值
                Matcher m_input = pattern_input.matcher(line);
                Matcher m_output = pattern_output.matcher(line);
                if(m_input.find()){
                    lock = true;
                }
                if(m_output.find()){
                    lock = false;
                }
            }
        }
        return contentMap;
    }

    /**
     * 获取时长 格式:"00:00:10.68"
     */
    private static int getDuration(String timelen) {
        int min = 0;
        String regex = "0";
        String[] arr = timelen.split(":");
        if (arr[0].compareTo(regex) > 0) {
            //秒
            min += Integer.valueOf(arr[0]) * 60 * 60;
        }
        if (arr[1].compareTo(regex) > 0) {
            min += Integer.valueOf(arr[1]) * 60;
        }
        if (arr[2].compareTo(regex) > 0) {
            min += Math.round(Float.valueOf(arr[2]));
        }
        return min;
    }

    /**
     * 将音量的最大值调整到制定数值，并反回成功或者失败
     */
    public static OperationResult<Object> ajustVolumeBol(String ffmpegCommandPath, String inputFilePath,String tmpputFilePath,String outputFilePath, Float maxVolume, String qscale) {
        try {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            logger.info("**************开始调节音量【" + inputFilePath + "】**************");
            logger.info("参数：ffmpegCommandPath="+ffmpegCommandPath+",inputFilePath="+inputFilePath+",tmpputFilePath="+tmpputFilePath+",maxVolume="+maxVolume+",qscale="+qscale);
            //1.先统一减小音量
            FfmpegCommandBuilder setVolumeCommandBuilderFirst = new FfmpegCommandBuilder(ffmpegCommandPath);
            FfmpegCommand setVolumeCommandFirst = setVolumeCommandBuilderFirst.input(inputFilePath).threads("2")
                    //<数值> 以<数值>质量为基础的VBR，取值0.01-255，越小视频画质越好, 如果用了-vcodec参数则不需要改参数
                    .qscale(qscale).volume(BusinessConstant.FIRST_MAX_VOLUME).override().vcodec(BusinessConstant.VCODEC_COPY).output(tmpputFilePath).build();
            setVolumeCommandFirst.start();
            //2.获取减小音量后的视频的具体信息
            FfmpegCommandBuilder getMaxVolumeCommandBuilder = new FfmpegCommandBuilder(ffmpegCommandPath);
            FfmpegCommand getMaxVolumeCommand = getMaxVolumeCommandBuilder.input(tmpputFilePath).volumeDetect().build();
            List<String> strings = getMaxVolumeCommand.startReturnList();
            logger.info("********************"+ JSONObject.toJSONString(strings));
            Map<String, Object> contentMap = parseContentToMap(strings);
            if (null == contentMap || contentMap.size() == 0) {
                logger.info("解析文件【" + inputFilePath + "】失败");
                return OperationResult.newValidateFailOperationResult("解析视频失败");
            }
            logger.info("原视频信息：" + contentMap.toString());

            // 视频文件数据速率
            Object vBitrateObject = contentMap.get(FfmpegEnum.VBITRATE.getValue());
            //视频文件最大音量
            Object volume = contentMap.get(FfmpegEnum.MAX_VOLUME.getValue());
            //视频的x方向分辨率
            Object xResolutionO = contentMap.get(FfmpegEnum.XRESOLUTION.getValue());
            //视频的y方向分辨率
            Object yResolutionO = contentMap.get(FfmpegEnum.YRESOLUTION.getValue());
            //视频转码格式是否符合要求
            Object passObject = contentMap.get(FfmpegEnum.PASSMUSTER.getValue());
            // 检测视频分辨率是否超标
            int xResolution = Integer.parseInt(xResolutionO.toString());
            int yResolution = Integer.parseInt(yResolutionO.toString());
            if(xResolution>BusinessConstant.MAX_RESOLUTION || yResolution>BusinessConstant.MAX_RESOLUTION){
                return OperationResult.newValidateFailOperationResult("视频分辨率大于"+BusinessConstant.MAX_RESOLUTION);
            }
            // 原来视频的比特率（视频部分的数据速率，不包含声音）
            int vBitrate = Integer.parseInt(vBitrateObject.toString())*1000;
            // 原来的视频编码是否符合要求
            String passMuster = passObject.toString();

            //3.转换成最终需要的视频
            FfmpegCommandBuilder setVolumeCommandBuilder = new FfmpegCommandBuilder(ffmpegCommandPath);
            setVolumeCommandBuilder
                    //输入文件地址
                    .input(tmpputFilePath)
                    //开启多个线程执行
                    .threads("2")
                    //覆盖输出文件
                    .override()
                    // 设置视频的比特率
                    .vb(vBitrate);

            // *添加转码格式
            if(BusinessConstant.VIDEO_PASS_SUCCESS.equals(passMuster)){
                setVolumeCommandBuilder.vcodec(BusinessConstant.VCODEC_COPY);
                // 使用原来的地址
                outputFilePath = inputFilePath;
            }else{
                setVolumeCommandBuilder.vcodec(BusinessConstant.VCODEC_SVID);
            }

            // *添加调整声音的参数
            if (null == volume) {
                logger.warn("原视频无声音, 无需调节");
            } else {
                float maxVolumeInFile = Float.parseFloat(volume.toString());
                //计算需要调节的音量值
                float adjustVolume = maxVolume - maxVolumeInFile;
                logger.info("设置前最大音量：" + maxVolumeInFile + "， 设置最大音量：" + maxVolume + "， 需要调节的音量：" + adjustVolume);
                //构建调节音量命令
                //需要修正的音量值（正数为提升，负数为降低音量）
                setVolumeCommandBuilder.volume(adjustVolume);
            }
            FfmpegCommand setVolumeCommand = setVolumeCommandBuilder.output(outputFilePath).build();
            setVolumeCommand.start();

            stopWatch.stop();
            long total = stopWatch.getTotalTimeMillis();
            logger.info("**************调节音量【" + inputFilePath + "】用时：" + total + "ms**************");
        } catch (Exception e) {
            logger.error("**************调节音量异常：" + e.getMessage(),e);
            return OperationResult.newValidateFailOperationResult("视频转码异常");
        }
        return OperationResult.newSuccessOperationResult(outputFilePath);
    }
}
